﻿// *************************************************************************************************
// Project: RealSense.Light.Core
// Namespace: RealSense.Light.Core
// File: SpeechSynthesizer.cs  Author: Peter O'Hanlon
// Copyright © Peter O'Hanlon 2014-2015
// Date: 2015/1/5 22:20
// *************************************************************************************************
using RealSense.Light.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RealSense.Light.Core
{
  public class SpeechSynthesizer : RealSenseBase, ISpeechSynthesizer
  {
    private int currentVolume = 80;
    private int pitch = 100;
    private int speechRate = 100;
    private string module = string.Empty;
    private ISpeechSynthesizerValidation validator;
    private ISession session;
    private int currentSentenceIndex = 0;

    public SpeechSynthesizer(ISession session, ISpeechSynthesizerValidation validator)
    {
      this.session = session;
      this.validator = validator;
    }

    public ISpeechSynthesizer SetSpeechModule(string module)
    {
      this.module = module;
      return this;
    }

    public ISpeechSynthesizer SetVolume(int volume)
    {
      if (!validator.IsValidVolume(volume))
      {
        throw new ArgumentOutOfRangeException("volume", "The volume must be between 0 and 100.");
      }
      currentVolume = volume;
      return this;
    }

    public ISpeechSynthesizer SetPitch(int pitch)
    {
      if (!validator.IsValidPitch(pitch))
      {
        throw new ArgumentOutOfRangeException("pitch", "The pitch must be between 50 and 200.");
      }

      this.pitch = pitch;
      return this;
    }

    public ISpeechSynthesizer SetSpeechRate(int speechRate)
    {
      if (!validator.IsValidSpeechRate(speechRate))
      {
        throw new ArgumentOutOfRangeException("speechRate", "The speech rate must be between 50 and 400.");
      }
      this.speechRate = speechRate;
      return this;
    }

    public void Say(string text)
    {
      // If the text is empty, there's no point trying to say it.
      if (string.IsNullOrWhiteSpace(text))
      {
        return;
      }

      using (PXCMSpeechSynthesis speechSynthesis = ServiceLocator.Instance.Get<IConnection>()
        .ConnectToDevice<PXCMSpeechSynthesis>(module, PXCMSpeechSynthesis.CUID))
      {
        var profile = SetProfile(speechSynthesis);
        SaySentence(text, speechSynthesis, profile);
      }
    }

    private void SaySentence(string text, PXCMSpeechSynthesis speechSynthesis, PXCMSpeechSynthesis.ProfileInfo profile)
    {
      int sentenceIndex = Interlocked.Increment(ref currentSentenceIndex);
      InvokeAndThrow(() => speechSynthesis.BuildSentence(sentenceIndex, text), "creating the sentence " + text);

      try
      {
        using (VoiceBuilder builder = new VoiceBuilder(profile.outputs.nchannels, profile.outputs.sampleRate))
        {
          // Retrieve the number of buffers for the sentence.
          int buffers = speechSynthesis.QueryBufferNum(sentenceIndex);
          for (int index = 0; index < buffers; index++)
          {
            PXCMAudio audio = speechSynthesis.QueryBuffer(sentenceIndex, index);
            if (audio == null)
            {
              break;
            }

            // Now, write the audio output.
            builder.WriteToAudioStream(audio);
          }
        }
      }
      finally
      {
        Interlocked.Decrement(ref sentenceIndex);
        speechSynthesis.ReleaseSentence(sentenceIndex);
      }
    }

    private PXCMSpeechSynthesis.ProfileInfo SetProfile(PXCMSpeechSynthesis speechSynthesis)
    {
      PXCMSpeechSynthesis.ProfileInfo profile = null;
      InvokeAndThrow(() => speechSynthesis.QueryProfile(out profile), "retrieving speech synthesizer profile");

      profile.volume = currentVolume;
      profile.pitch = pitch;
      profile.rate = speechRate;

      InvokeAndThrow(() => speechSynthesis.SetProfile(profile), "setting speech synthesizer profile");

      return profile;
    }

    protected override void Dispose(bool disposing)
    {
      // There's nothing to dispose here.
    }
  }
}